//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//
/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

using EffiProz.Core.Lib;
using EffiProz.Core.Persist;


namespace EffiProz.Core
{


    // fredt@users 20020210 - patch 513005 by sqlbob@users (RMP) - ABS function
    // fredt@users 20020305 - patch 1.7.0 - change to 2D string arrays
    // sqlbob@users 20020420- patch 1.7.0 - added HEXTORAW and RAWTOHEX.
    // boucherb@user 20020918 - doc 1.7.2 - added JavaDoc  and code comments
    // fredt@user 20021021 - doc 1.7.2 - modified JavaDoc
    // boucherb@users 20030201 - patch 1.7.2 - direct calls for EffiProz.Core.Library
    // fredt@users - patch 1.8.0 - new functions added

    /**
     * fredt - todo - since the introduction of SQL built-in functions and
     * evaluation of several session-dependent methods outside this class,
     * several methods here are dummies. These methods are still reported in
     * system tables incorrectly as corresponding to the SQL function names.
     */

    /**
     * Provides the HSQLDB implementation of standard Open Group SQL CLI
     * <em>Extended Scalar Functions</em> and other public HSQLDB SQL functions.<p>
     *
     * All methods here that have a Connection parameter are dummies and should
     * not be called from user supplied Java procedure or trigger code. Use real
     * SQL functions should be called instead _in these instances.
     *
     * Extensively rewritten and extended _in successive versions of HSQLDB.
     *
     * @author Thomas Mueller (Hypersonic SQL Group)
     * @version 1.8.0
     * @since Hypersonic SQL
     */
    public class Library
    {
        private Library() { }

        /**
         * Retrieves a <em>magically</em> rounded </code>double</code> value produced
         * from the given <code>double</code> value.  This method provides special
         * handling for numbers close to zero and performs rounding only for
         * numbers within a specific range, returning  precisely the given value
         * if it does not lie in this range. <p>
         *
         * Special handling includes: <p>
         *
         * <UL>
         * <LI> input in the interval -0.0000000000001..0.0000000000001 returns 0.0
         * <LI> input outside the interval -1000000000000..1000000000000 returns
         *      input unchanged
         * <LI> input is converted to String form
         * <LI> input with a <code>String</code> form length greater than 16 returns
         *      input unchaged
         * <LI> <code>String</code> form with last four characters of '...000x' where
         *      x != '.' is converted to '...0000'
         * <LI> <code>String</code> form with last four characters of '...9999' is
         *      converted to '...999999'
         * <LI> the <code>java.lang.Double.doubleValue</code> of the <code>String</code>
         *      form is returned
         * </UL>
         * @author Thomas Mueller (Hypersonic SQL Group)
         * @param d the double value for which to retrieve the <em>magically</em>
         *      rounded value
         * @return the <em>magically</em> rounded value produced
         */
        public static double roundMagic(double d)
        {

            // this function rounds numbers in a good way but slow:
            // - special handling for numbers around 0
            // - only numbers <= +/-1000000000000
            // - convert to a string
            // - check the last 4 characters:
            // '000x' becomes '0000'
            // '999x' becomes '999999' (this is rounded automatically)
            if ((d < 0.0000000000001) && (d > -0.0000000000001))
            {
                return 0.0;
            }

            if ((d > 1000000000000.0) || (d < -1000000000000.0))
            {
                return d;
            }

            StringBuilder sb = new StringBuilder();

            sb.Append(d);

            int len = sb.Length;

            if (len < 16)
            {
                return d;
            }

            char cx = sb[len - 1];
            char c1 = sb[len - 2];
            char c2 = sb[len - 3];
            char c3 = sb[len - 4];

            if ((c1 == '0') && (c2 == '0') && (c3 == '0') && (cx != '.'))
            {
                sb[len - 1] = '0';
            }
            else if ((c1 == '9') && (c2 == '9') && (c3 == '9') && (cx != '.'))
            {
                sb[len - 1] = '9';
                sb.Append('9');
                sb.Append('9');
            }

            return Double.Parse(sb.ToString());
        }

        /**
         * Returns the given <code>double</code> value, rounded to the given
         * <code>int</code> places right of the decimal point. If
         * the supplied rounding place value is negative, rounding is performed
         * to the left of the decimal point, using its magnitude (absolute value).
         * @param d the value to be rounded
         * @param p the rounding place value
         * @return <code>d</code> rounded
         */
        public static double round(double d, int p)
        {

            double f = Math.Pow(10.0, p);

            return Math.Round(d * f) / f;
        }

        // STRING FUNCTIONS

        /**
         * Returns the character string corresponding to the given ASCII
         * (or Unicode) value.
         *
         * <b>Note:</b> <p>
         *
         * In some SQL CLI
         * implementations, a <code>null</code> is returned if the range is outside 0..255.
         * In HSQLDB, the corresponding Unicode character is returned
         * unchecked.
         * @param code the character code for which to return a String
         *      representation
         * @return the String representation of the character
         */
        public static String character(int code)
        {
            return ((char)code).ToString();
        }

        /**
         * Returns a count of the characters that do not match when comparing
         * the 4 digit numeric SOUNDEX character sequences for the
         * given <code>String</code> objects.  If either <code>String</code> object is
         * <code>null</code>, zero is returned.
         * @param s1 the first <code>String</code>
         * @param s2 the second <code>String</code>
         * @return the number of differences between the <code>SOUNDEX</code> of
         *      <code>s1</code> and the <code>SOUNDEX</code> of <code>s2</code>
         */

        // fredt@users 20020305 - patch 460907 by fredt - soundex
        public static int difference(String s1, String s2)
        {

            /** @todo: check if this is the standard algorithm */
            if ((s1 == null) || (s2 == null))
            {
                return 0;
            }

            s1 = soundex(s1);
            s2 = soundex(s2);

            int e = 0;

            for (int i = 0; i < 4; i++)
            {
                if (s1[i] != s2[i])
                {
                    e++;
                }
            }

            return e;
        }

        /**
         * Returns the starting position of the first occurrence of
         * the given <code>search</code> <code>String</code> object within
         * the given <code>String</code> object, <code>s</code>.
         *
         * The search for the first occurrence of <code>search</code> begins with
         * the first character position in <code>s</code>, unless the optional
         * argument, <code>start</code>, is specified (non-null). If
         * <code>start</code> is specified, the search begins with the character
         * position indicated by the value of <code>start</code>, where the
         * first character position in <code>s</code> is indicated by the value 1.
         * If <code>search</code> is not found within <code>s</code>, the
         * value 0 is returned.
         * @param search the <code>String</code> occurence to find in <code>s</code>
         * @param s the <code>String</code> within which to find the first
         *      occurence of <code>search</code>
         * @param start the optional character position from which to start
         *      looking in <code>s</code>
         * @return the one-based starting position of the first occurrence of
         *      <code>search</code> within <code>s</code>, or 0 if not found
         */
        public static int locate(String search, String s, int? start)
        {

            if (s == null || search == null)
            {
                return 0;
            }

            int i = (start == null) ? 0
                                    : (int)start - 1;

            return s.IndexOf(search, (i < 0) ? 0
                                             : i) + 1;
        }

        /**
         * Returns a <code>String</code> composed of the given <code>String</code>,
         * repeated  <code>count</code> times.
         *
         * @param s the <code>String</code> to repeat
         * @param count the number of repetitions
         * @return the given <code>String</code>, repeated <code>count</code> times
         */
        public static String repeat(String s, int? count)
        {

            if (s == null || count == null || count < 0)
            {
                return null;
            }

            int i = (int)count;
            StringBuilder sb = new StringBuilder(s.Length * i);

            while (i-- > 0)
            {
                sb.Append(s);
            }

            return sb.ToString();
        }

        // fredt@users - 20020903 - patch 1.7.1 - bug fix to allow multiple replaces

        /**
         * Replaces all occurrences of <code>replace</code> in <code>s</code>
         * with the <code>String</code> object: <code>with</code>
         * @param s the target for replacement
         * @param replace the substring(s), if any, in <code>s</code> to replace
         * @param with the value to substitute for <code>replace</code>
         * @return <code>s</code>, with all occurences of <code>replace</code>
         *      replaced by <code>with</code>
         */
        public static String replace(String s, String replace, String with)
        {

            if (s == null || replace == null)
            {
                return s;
            }

            if (with == null)
            {
                with = "";
            }

            StringBuilder b = new StringBuilder();
            int start = 0;
            int lenreplace = replace.Length;

            while (true)
            {
                int i = s.IndexOf(replace, start);

                if (i == -1)
                {
                    b.Append(s.Substring(start));

                    break;
                }

                b.Append(s.Substring(start, i - start));
                b.Append(with);

                start = i + lenreplace;
            }

            return b.ToString();
        }

        // fredt@users 20011010 - patch 460907 by fredt - soundex

        /**
         * Returns a four character code representing the sound of the given
         * <code>String</code>. Non-ASCCI characters in the
         * input <code>String</code> are ignored. <p>
         *
         * This method was
         * rewritten for HSQLDB by fredt@users to comply with the description at
         * <a href="http://www.archives.gov/genealogy/census/soundex.html">
         * http://www.archives.gov/genealogy/census/soundex.html </a>.<p>
         * @param s the <code>String</code> for which to calculate the 4 character
         *      <code>SOUNDEX</code> value
         * @return the 4 character <code>SOUNDEX</code> value for the given
         *      <code>String</code>
         */
        public static String soundex(String s)
        {

            if (s == null)
            {
                return s;
            }

            s = s.ToUpper(System.Globalization.CultureInfo.InvariantCulture);

            int len = s.Length;
            char[] b = new char[] {
            '0', '0', '0', '0'
        };
            char lastdigit = '0';

            for (int i = 0, j = 0; i < len && j < 4; i++)
            {
                char c = s[i];
                char newdigit;

                if ("AEIOUY".IndexOf(c) != -1)
                {
                    newdigit = '7';
                }
                else if (c == 'H' || c == 'W')
                {
                    newdigit = '8';
                }
                else if ("BFPV".IndexOf(c) != -1)
                {
                    newdigit = '1';
                }
                else if ("CGJKQSXZ".IndexOf(c) != -1)
                {
                    newdigit = '2';
                }
                else if (c == 'D' || c == 'T')
                {
                    newdigit = '3';
                }
                else if (c == 'L')
                {
                    newdigit = '4';
                }
                else if (c == 'M' || c == 'N')
                {
                    newdigit = '5';
                }
                else if (c == 'R')
                {
                    newdigit = '6';
                }
                else
                {
                    continue;
                }

                if (j == 0)
                {
                    b[j++] = c;
                    lastdigit = newdigit;
                }
                else if (newdigit <= '6')
                {
                    if (newdigit != lastdigit)
                    {
                        b[j++] = newdigit;
                        lastdigit = newdigit;
                    }
                }
                else if (newdigit == '7')
                {
                    lastdigit = newdigit;
                }
            }

            return new String(b, 0, 4);
        }

        /**
         * Returns the characters from the given <code>String</code>, starting at
         * the indicated one-based <code>start</code> position and extending the
         * (optional) indicated <code>length</code>. If <code>length</code> is not
         * specified (is <code>null</code>), the remainder of <code>s</code> is
         * implied.
         *
         * The rules for boundary conditions on s, start and length are,
         * in order of precedence: <p>
         *
         * 1.) if s is null, return null
         *
         * 2.) If length is less than 1, return null.
         *
         * 3.) If start is 0, it is treated as 1.
         *
         * 4.) If start is positive, count from the beginning of s to find
         *     the first character postion.
         *
         * 5.) If start is negative, count backwards from the end of s
         *     to find the first character.
         *
         * 6.) If, after applying 2.) or 3.), the start position lies outside s,
         *     then return null
         *
         * 7.) if length is ommited or is greated than the number of characters
         *     from the start position to the end of s, return the remaineder of s,
         *     starting with the start position.
         *
         * @param s the <code>String</code> from which to produce the indicated
         *      substring
         * @param start the starting position of the desired substring
         * @param length the length of the desired substring
         * @return the indicted substring of <code>s</code>.
         */

        /**
         * Retrieves the full version number of this database product. <p>
         *
         * @return database version number as a <code>String</code> object
         * @since 1.8.0.4
         */
        public static String getDatabaseFullProductVersion()
        {
            return EffiProzDatabaseProperties.THIS_FULL_VERSION;
        }

        /**
         * Retrieves the name of this database product. <p>
         *
         * @return database product name as a <code>String</code> object
         * @since 1.7.2
         */
        public static String getDatabaseProductName()
        {
            return EffiProzDatabaseProperties.PRODUCT_NAME;
        }

        /**
         * Retrieves the version number of this database product. <p>
         *
         * @return database version number as a <code>String</code> object
         * @since 1.7.2
         */
        public static String getDatabaseProductVersion()
        {
            return EffiProzDatabaseProperties.THIS_VERSION;
        }

        /**
         * Retrieves the major version number of this database. <p>
         *
         * @return the database's major version as an <code>int</code> value
         * @since 1.7.2
         */
        public static int getDatabaseMajorVersion()
        {
            return EffiProzDatabaseProperties.MAJOR;
        }

        /**
         * Retrieves the major version number of this database. <p>
         *
         * @return the database's major version as an <code>int</code> value
         * @since 1.7.2
         */
        public static int getDatabaseMinorVersion()
        {
            return EffiProzDatabaseProperties.MINOR;
        }
    }
}